local type,tostring,select,pairs,ipairs,assert=type,tostring,select,pairs,ipairs,assert
local string_len,string_rep,string_match,string_format,string_find,string_byte=
	  string.len,string.rep,string.match,string.format,string.find,string.byte
local table_insert,table_remove=
	  table.insert,table.remove
module(..., package.seeall)

require'zdc.logger'
require'zdc.node'
require'zdc.fieldutil'
require'zdc.typeex'
require'zdc.deflate'
require'zdc.chglog'
require'zdc.dblabel'
local nodeapi=require'zdc.nodeapi'

local dfskey=zdc.fieldutil.dfskey

DplTreeTag = {}
local DplTreeTag_mt = {__metatable={}, __index=DplTreeTag}


function new(dal,dpltr,_wrap,_treeid,childmt)
	local repo = setmetatable({},childmt or DplTreeTag_mt)
	repo.dal=dal
	repo.dpltr=dpltr
	local _trees=dpltr._trees
	if not _trees then
		_trees={}
		dpltr._trees=_trees
	end
	_trees[repo]=repo
	repo._pto_id = dpltr._pto_id
	repo._db = dpltr._db
	repo._secugroups = dpltr._secugroups
	repo._auth=dpltr._auth
	repo._nodenew = dpltr._nodenew
	repo._sb = dpltr._sb
	repo._log = dpltr._log
	repo._treeid=_treeid
	repo._wrap=_wrap
	repo._talist={}
	repo:_reset_cache(true)
	return repo
end



--[[
1: save node
2: remove node
3: remove node with sub
--]]
function DplTreeTag:_ta_inta()
	return self.transowner
end
function DplTreeTag:_ta_abort()
	print(self.dal.gimtag .. ' do a discard')
	self._base:_discard(true)
end
function DplTreeTag:_ta_save_node(id)
	assert(nil,'DplTreeTag:_ta_save_node')
end
function DplTreeTag:_ta_remove_node(id,deep)
	assert(nil,'DplTreeTag:_ta_remove_node')
end
function DplTreeTag:_ta_remove_subnodes(parentid,deep)
	assert(nil,'DplTreeTag:_ta_remove_subnodes')
end

function DplTreeTag:_drop_all(opt)
	local label=opt and opt.label
	local nl
	self._db:transdo(function()
		self.dpltr:_remove_node(self._treeid,true)
		nl=zdc.dblabel.label_inc(self._db,self._treeid,nil,label)
	end)
	self.dpltr:_reset_cache(true)
	for k,v in pairs(self.dpltr._trees) do
		k:_reset_cache(true)
	end
	--self:Reset()
	local rec=zdc.chglog.new()
	rec._drop_all=true
	rec.label=nl
	self.dal:_post_chglist(self,rec,rec)
	return nl
end

function DplTreeTag:p_Commit(bsave,bresultall,bresult,label,timestamp,RN,dbrec)
	if bsave then
		local _db=self._db
		local nl
		--print(self.dal.gimtag .. ' save db')
		_db:transdo(function()
			RN:__save(true)
			nl=zdc.dblabel.label_inc(_db,RN.__id,timestamp,label)
		end)
		dbrec.label=nl
	end
end

local function _get_oammeta()
	if type(OAMMETA)=='userdata' and type(OAMMETA.set_dbstat)=='function' then
		return OAMMETA
	end 
end

function DplTreeTag:_ta_commit(opt)
	local bsave,bresultall,bresult,label,timestamp
	if type(opt)=='table' then
		bsave,bresultall,bresult,label,timestamp=opt.bsave,opt.bresultall,opt.bresult,opt.label,opt.timestamp
	end
	print(self.dal.gimtag .. ' do a commit')
	local o = _get_oammeta()
	local flash = false
	label=label or ''
	local RN=self._base
	RN:_shrink()
	local rec,dbrec=RN:_commit(true,zdc.chglog.new(),zdc.chglog.new())
	if rec:_noempty() then
		rec.label='|||' .. (label or '')
		for k,v in pairs(self.dpltr._trees) do
			if k~=self then
				if k.transowner then
					--print('commit trans, and merge to in-trans tree ' .. k.dal.gimtag)
					k:_mergeothertree(rec)
				else
					--print('commit trans, and merge to tree ' .. k.dal.gimtag)
					k:_mergeothertree(rec)
				end
			end
		end
		if dbrec:_noempty() then
			if o then 
				o:set_dbstat(1,0,1) 
				flash = true
			end
			local r,msg=pcall(self.p_Commit,self,bsave,bresultall,bresult,label,timestamp,RN,dbrec)
			if not r then
				print('ERROR:','p_Commit err',msg)
				error(msg)
			end
		end
		self.dal:_post_chglist(self,rec,dbrec)
	end
	local ra,rb
	if bresult then
		rb = dbrec:_gettable()
	else
		rb = dbrec.label or ''
	end
	if bresultall then
		ra = rec:_gettable()
	else
		ra = rec.label or ''
	end

	if o and flash then o:set_dbstat(0,2,1) end
	return ra,rb
end

function DplTreeTag:_mergeothertree(rec)
	if not self._wrap or (#rec._d==0 and #rec._a==0) then return end
	local udt={}
	for i,v in ipairs(rec._d) do
		udt[v:GetParentId()]=true
	end
	for i,v in ipairs(rec._a) do
		udt[v:GetParentId()]=true
	end
	-- update wrap obj
	udt=odc.idop.mergeroot(udt)
	for i,v in pairs(udt) do
		local wo=self:GetNode_r(v)
		if wo and wo._wrap_child then
			print(self.dal.gimtag,'update wrap obj ' .. v)
			wo:_wrap_child()
		end
	end
end

function DplTreeTag:_reset_cache(force)
	print('???','DplTreeTag:_reset_cache',force)
	local id = self._treeid
	if self._wrap then
		self._base = self._wrap(self.dpltr:_get_node(id))
	else
		self._base = self.dpltr:_get_node(id)
	end
end

function DplTreeTag:_put_cache(node)
	assert(nil,'DplTreeTag:_put_cache(node) ???')
end

function DplTreeTag:_del_sub_cache(idparent)
	assert(nil,'DplTreeTag:_del_sub_cache(node) ???')
end

function DplTreeTag:_del_cache(id,deep)
	assert(nil,'DplTreeTag:_del_cache(node) ???')
end

--TODO: optim
function DplTreeTag:_has_node(id)
	local cn=self._base
	if id==cn.__id then return cn end
	local prefix = cn._id .. '/'
	local preflen = #prefix
	if #id > preflen and id:sub(1, preflen) == prefix then
		for sub in string.gmatch(id:sub(preflen),'[^/]+') do
			cn = cn:__get_child(sub)
			if not cn then return nil,'EN1NE:'..id end
		end
		return cn
	end
	return nil,'EN1NE:'..id
end

function DplTreeTag:_get_node(id)
	local cn=self._base
	if id==cn.__id then return cn end
	local prefix = cn.__id .. '/'
	local preflen = #prefix
	if #id > preflen and id:sub(1, preflen) == prefix then
		for sub in string.gmatch(id:sub(preflen),'[^/]+') do
			cn = cn:__get_child(sub)
			if not cn then return nil,'EN1NE:'..id end
		end
		return cn
	end
	return nil,'EN1NE:'..id
end

function DplTreeTag:_deflate_node(node,fields,dyfields)
	return self.dpltr:_deflate_node(node,fields,dyfields)
end

function DplTreeTag:_sure_node(id,mc,nocache)
	local cn=self._base
	if id==cn.__id then return cn end
	local prefix = cn._id .. '/'
	local preflen = #prefix
	if #id > preflen and id:sub(1, preflen) == prefix then
		for sub in string.gmatch(id:sub(preflen),'[^/]+') do
			cn = cn:__sure_child(sub)
			if not cn then return nil,'EN1NE:'..id end
		end
		return cn
	end
	return nil,'EN1NE:'..id
end

function DplTreeTag:_make_node(id,data,ik,ts,firstcreate,preload)
	assert(nil,'DplTreeTag:_make_node(node) ???')
end

function DplTreeTag:_save_node(node)
	return self.dpltr:_save_node(node)
end

function DplTreeTag:_save_data(id,nodedata)
	assert(nil,'DplTreeTag:_save_data(node) ???')
end

function DplTreeTag:_call_node(user,id,act,...)
	local n=self:_get_node(id)
	if not n then return false,'EN1NE:'..tostring(id) end
	return nodeapi.callact(user,self.dal,n,act,...)
end
function DplTreeTag:_acall_node(user,id,cb,act,...)
	local n=self:_get_node(id)
	if not n then return false,'EN1NE:'..tostring(id) end
	return nodeapi.asynccall(user,self.dal,cb,n,act,...)
end

function DplTreeTag:_get_child_nodes(parentid,cn)
	local n,err=self:_get_node(parentid)
	if not n then return cn,err end
	return n:__get_child_all(cn)
end

function DplTreeTag:_get_child_nodes_ex(parentid,cn,where,sort,limit)
	assert(nil,'DplTreeTag:_get_child_nodes_ex TODO')
end

function DplTreeTag:_get_child_rawfields_ex(parentid,fields,where,sort,limit)
	assert(nil,'DplTreeTag:_get_child_rawfields_ex TODO')
end

function DplTreeTag:_get_prefix_nodes_ex(prefix,cn,where,sort,limit)
	assert(nil,'DplTreeTag:_get_prefix_nodes_ex TODO')
end

function DplTreeTag:_get_child_ids_ex(parentid,where,sort,limit)
	assert(nil,'DplTreeTag:_get_child_ids_ex TODO')
end

function DplTreeTag:_get_prefix_ids_ex(prefix,where,sort,limit)
	assert(nil,'DplTreeTag:_get_prefix_ids_ex TODO')
end

function DplTreeTag:_get_child_cnt_ex(parentid,where)
	assert(nil,'DplTreeTag:_get_child_cnt_ex TODO')
end

function DplTreeTag:_get_prefix_cnt_ex(prefix,where)
	assert(nil,'DplTreeTag:_get_prefix_cnt_ex TODO')
end

function DplTreeTag:_remove_node(id,deep)
	return self.dpltr:_remove_node(id,deep)
end

function DplTreeTag:_remove_subnodes(idparent)
	local pn,err=self:_get_node(idparent)
	if not pn then return  end

	local chds=self:_get_child_nodes(idparent)
	for k,_ in pairs(chds) do
		pn:__remove_child(k,true)
	end
end
